#!/usr/bin/perl -w
# flickr2pics - Copying your Flickr photos to livedoor PICS
#
# $Id: flickr2pics 356 2007-02-11 06:44:52Z ogawa $
#
# This software is provided as-is. You may use it for commercial or 
# personal use. If you distribute it, please keep this notice intact.
#
# Copyright (c) 2006 Hirotaka Ogawa

use strict;
use YAML;
use Pod::Usage;
use Getopt::Long;

our $VERSION = '0.01';

my %opt = (conf => 'config.yaml');
GetOptions(\%opt, 'help', 'conf=s') or pod2usage(2);
pod2usage(1) if $opt{help};

my $cfg = YAML::LoadFile($opt{conf});
die "Cannot find proper configuration for flickr2pics."
    unless $cfg->{flickr_username} && $cfg->{pics_username} && $cfg->{pics_password};

local $| = 1;

my $flickr = new FlickrPublicPhoto::API();
print "Fetching your Flickr info.\n";
my @photos = $flickr->photos($cfg->{flickr_username})
    or die "Failed. Or you don't have any public Flickr photos.\n";
print "Finished.\n";
@photos = reverse @photos; # oldest first

use XML::Atom::Client;
use XML::Atom::Entry;
use XML::Atom::Content;
use LWP::UserAgent;
use constant NS_DC => 'http://purl.org/dc/elements/1.1/';

my $pics_url = 'http://ws.pics.livedoor.com/atom/' . $cfg->{pics_username};
my $pics = XML::Atom::Client->new;
$pics->username($cfg->{pics_username});
$pics->password($cfg->{pics_password});

my $ua = LWP::UserAgent->new;

foreach my $photo (@photos) {
    print "\nDownloading " . $photo->id . " from Flickr.\n";
    my $res = $ua->get($photo->img_url);
    die "Failed." unless $res->is_success;
    print "Downloaded.\n";

    my $content = XML::Atom::Content->new;
    $content->body($res->content);
    $content->type($photo->mime_type);

    my $entry = XML::Atom::Entry->new;
    $entry->title($photo->title);
    $entry->content($content);

    my $root = $entry->{doc}->getDocumentElement;
    foreach my $tag ($photo->tags) {
	my $elem = $entry->{doc}->createElementNS(NS_DC, 'subject');
	$root->appendChild($elem);
	$elem->appendChild(XML::LibXML::Text->new($tag));
    }

    print "Uploading " . $photo->id . " to livedoor PICS.\n";
    $pics->createEntry($pics_url, $entry)
	or die "PICS request failed: " . $pics->errstr . "\n";
    print "Finished.\n";
}

package FlickrPublicPhoto;

sub new {
    my ($class, $params) = @_;
    $params ||= {};
    bless $params, $class;
}

sub id { $_[0]->{id} || '' }
sub title { $_[0]->{title} || '' }
sub url { $_[0]->{url} || '' }
sub description { $_[0]->{description} || '' }

sub img_url {
    my $this = shift;
    'http://static.flickr.com/' . $this->{server} . '/' . $this->{id} . '_' . $this->{secret} . '_o.' . $this->{format};
}

sub mime_type {
    my $format = $_[0]->{format} || 'jpeg';
    $format = lc $format;
    $format = 'jpeg' if $format eq 'jpg';
    'image/' . $format;
}

sub tags {
    my $tags = $_[0]->{tags};
    $tags ? @$tags : ();
}

package FlickrPublicPhoto::API;
use base 'Flickr::API';
use XML::LibXML;
use constant API_KEY => '9765114fb37045ea8d2ca9d813e24b63';

sub new {
    my ($class, $params) = @_;
    my $key = $params->{key} || API_KEY;
    bless $class->SUPER::new({ key => $key }), $class;
}

sub resolve_nsid {
    my $this = shift;
    my ($uname) = @_;
    return $uname if $uname =~ /^\d+\@N\d{2}$/;

    my $res = ($uname =~ /^[^@]+@[^.]+\..+/) ?
	$this->execute_method('flickr.people.findByEmail',
			      { find_email => $uname }) :
	$this->execute_method('flickr.people.findByUsername',
			      { username => $uname });
    die "Flickr request failed: " . $res->{error_message} . "\n"
	unless $res->{success} == 1;

    my $parser = XML::LibXML->new;
    my $xml = $parser->parse_string($res->content);
    $xml->findvalue('//user/@id');
}

sub photos {
    my $this = shift;
    my ($uname) = @_;
    my $nsid = $this->resolve_nsid($uname);
    my $parser = XML::LibXML->new;

    my @photo_ids;
    my ($page, $pages) = (1, 0);
    do {
	my $res = $this->execute_method('flickr.people.getPublicPhotos',
					{ user_id => $nsid, page => $page });
	die "Flickr request failed: " . $res->{error_message} . "\n"
	    unless $res->{success} == 1;
	my $xml = $parser->parse_string($res->content);
	$pages ||= $xml->findvalue('//photos/@pages');
	for my $node ($xml->findnodes('//photos/photo/@id')) {
	    push @photo_ids, $node->getValue;
	}
    } while ($page++ < $pages);

    my @photos = ();
    for my $photo_id (@photo_ids) {
	my $res = $this->execute_method('flickr.photos.getInfo',
					{ photo_id => $photo_id });
	die "Flickr request failed: " . $res->{error_message} . "\n"
	    unless $res->{success} == 1;
	my $xml = $parser->parse_string($res->content);
	my $node = $xml->findnodes('//photo')->get_node(0) or next;

	my $photo = new FlickrPublicPhoto();
	$photo->{id}		= $photo_id;
	$photo->{nsid}		= $nsid;
	$photo->{username}	= $node->findvalue('owner/@username');
	$photo->{realname}	= $node->findvalue('owner/@realname');
	$photo->{location}	= $node->findvalue('owner/@location');
	$photo->{secret}	= $node->findvalue('@secret');
	$photo->{server}	= $node->findvalue('@server');
	$photo->{format}	= $node->findvalue('@originalformat');
	$photo->{title}		= $node->findvalue('title/text()');
	$photo->{description}	= $node->findvalue('description/text()');
	$photo->{dateposted}	= $node->findvalue('dates/@posted');
	$photo->{datetaken}	= $node->findvalue('dates/@taken');
	$photo->{lastupdate}	= $node->findvalue('dates/@lastupdate');
	$photo->{url}		= $node->findvalue('urls/url/text()');
	my @tags;
	for my $tag ($node->findnodes('tags/tag/text()')) {
	    push @tags, $tag->getValue;
	}
	$photo->{tags}		= \@tags;

	push @photos, $photo;
    }
    return @photos;
}

1;
=head1 NAME

flickr2pics - Copying your Flickr photos to livedoor PICS

=head1 SYNOPSIS

  % flickr2pics
  % flickr2pics --conf /path/to/config.yaml

=head1 DESCRIPTION

C<flickr2pics> is a command line application that fetches Flickr photos
and stores them into livedoor PICS.

=head1 REQUIREMENT

This application requires perl 5.8.0 with following Perl modules
installed on your box.

=over 4

=item XML::Atom

=item XML::LibXML

=item Flickr::API

=item YAML

=back

=head1 OPTIONS

This application has a command-line option as follows:

=over 4

=item --conf /path/to/config.yaml

Specified the path to a configuration file. By default, C<config.yaml>
in the current directory.

=back

=head1 CONFIGURATION

The distributions includes a sample configuration file
C<config.yaml.sample>. You can rename it to C<config.yaml> and
configure C<flickr2pics>.

=over 4

=item flickr_username

Set your Flickr username or NSID. (e.g., 12345678@N00)

=item pics_username, pics_password

Set your username and password for livedoor PICS.

=back

=head1 DEVELOPMENT

The development version is always available from the following
subversion repository:

  http://code.as-is.net/svn/public/flickr2pics/trunk/

You can browse the files via Trac from the following:

  http://code.as-is.net/public/browser/flickr2pics/trunk/

Any comments, suggestions, or patches are welcome.

=head1 AUTHOR

Hirotaka Ogawa E<lt>hirotaka.ogawa at gmail.comE<gt>

This script is free software and licensed under the same terms as Perl
(Artistic/GPL).

=cut
